/*
 * Configuration.java
 *
 * Created on June 18, 2007, 3:01 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.www;

import org.atomojo.www.util.IdentityManager;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.atomojo.app.client.Link;
import org.atomojo.app.client.LinkSet;
import org.atomojo.www.util.ResourceManager;
import org.atomojo.www.util.script.ScriptManager;
import org.infoset.xml.Document;
import org.infoset.xml.DocumentLoader;
import org.infoset.xml.Element;
import org.infoset.xml.Name;
import org.infoset.xml.XMLException;
import org.infoset.xml.sax.SAXDocumentLoader;
import org.restlet.Application;
import org.restlet.Context;
import org.restlet.data.MediaType;
import org.restlet.data.Parameter;

/**
 *
 * @author alex
 */
public class Configuration
{
   public static final URI NAMESPACE = URI.create("http://www.atomojo.org/Vocabulary/WebServer/2008/1/0");
   static final Name SERVER = Name.create(NAMESPACE,"server");
   static final Name APPDEF = Name.create(NAMESPACE,"appdef");
   static final Name LIBRARY = Name.create(NAMESPACE,"library");
   static final Name INTERFACE = Name.create(NAMESPACE,"interface");
   static final Name AUTOCONF = Name.create(NAMESPACE,"autoconf");
   static final Name HOST = Name.create(NAMESPACE,"host");
   static final Name LINK = Name.create(NAMESPACE,"link");
   static final Name PARAMETER = Name.create(NAMESPACE,"parameter");
   static final Name LOG = Name.create(NAMESPACE,"log");
   static final Name APP = Name.create(NAMESPACE,"app");
   static final Name CONTENT = Name.create(NAMESPACE,"content");
   static final Name KEYSTORE = Name.create(NAMESPACE,"keystore");

   
   public class AppDef {
      String name;
      Class<Application> appClass;
      public AppDef(String name,Class<Application> appClass)
      {
         this.name = name;
         this.appClass = appClass;
      }
      
      public String getName() {
         return name;
      }
      
      public Application getInstance(Context context) 
         throws InstantiationException,IllegalAccessException,NoSuchMethodException,InvocationTargetException
      {
         Constructor<Application> makeit = appClass.getConstructor(Context.class);
         return makeit.newInstance(context);
      }
   }
   
   public class Interface {
      String addr;
      int port;
      boolean secure;
      Map<String,Host> hosts;
      LinkSet links;
      
      public Interface(String addr,int port,boolean secure)
      {
         this.addr = addr;
         this.port = port;
         this.secure = secure;
         this.hosts = new HashMap<String,Host>();
         this.links = new LinkSet();
      }
      
      public String getKey() {
         return this.addr+":"+this.port;
      }
      
      public String getAddress() {
         return addr;
      }
      
      public int getPort() {
         return port;
      }
      
      public boolean isSecure() {
         return secure;
      }
      
      public Map<String,Host> getHosts() {
         return hosts;
      }
      
      public LinkSet getLinks() {
         return links;
      }
      
      
   }

   public class Host {
      String name;
      String internalName;
      List<AppConf> apps;
      LinkSet links;
      List<Content> contents;
      Map<String,String> parameters;
      Map<String,String> logConf;
      
      public Host(String name)
      {
         this(name,null);
      }

      public Host(String name,String internalName)
      {
         this.name = name;
         this.internalName = internalName;
         this.apps = new ArrayList<AppConf>();
         this.links = new LinkSet();
         this.contents = new ArrayList<Content>();
         this.parameters = new TreeMap<String,String>();
         this.logConf = new HashMap<String,String>();
      }
      
      public String getName() {
         return name;
      }

      public String getInternalName() {
         return internalName;
      }

      public Map<String,String> getParameters() {
         return parameters;
      }

      public Map<String,String> getLogConfiguration() {
         return logConf;
      }

      public LinkSet getLinks() {
         return links;
      }
      
      public List<AppConf> getApplications() {
         return apps;
      }
      
      public List<Content> getContentSources() {
         return contents;
      }
   }
   
   public class Content {
      URI source;
      String match;
      Content(URI source,String match)
      {
         this.source = source;
         this.match = match;
      }
      
      public URI getSource() {
         return source;
      }
      
      public String getMatch() {
         return match;
      }
   }
   
   public class AppConf {
      AppDef appDef;
      String match;
      Element confElement;
      LinkSet links;
      Map<String,String> parameters;
      
      public AppConf(AppDef appDef,String match,Element confElement,LinkSet inheritedLinks)
      {
         this.appDef = appDef;
         this.match = match;
         this.confElement = confElement;
         this.links = new LinkSet();
         this.links.mergeLinkSet(inheritedLinks);
         this.parameters = new TreeMap<String,String>();
      }
      
      public AppDef getApplicationDef() {
         return  appDef;
      }
      
      public LinkSet getLinks() {
         return links;
      }
      
      public Map<String,String> getParameters() {
         return parameters;
      }
      
      public Element getConfiguration() {
         return confElement;
      }

      public Application getApplication(Context context,Map<String,List<Link>> links)
         throws InstantiationException,IllegalAccessException,NoSuchMethodException,InvocationTargetException
      {
         Context appContext = context.createChildContext();
         for (Parameter param : context.getParameters()) {
            String name = param.getName();
            if (name.equals("keystorePath") ||
                name.equals("keystorePassword") ||
                name.equals("keyPassword")) {
               continue;
            }
            appContext.getParameters().add(param);
         }
         IdentityManager manager = (IdentityManager)context.getAttributes().get(IdentityManager.ATTR);
         if (manager!=null) {
            appContext.getAttributes().put(IdentityManager.ATTR,manager);
         }
         appContext.getAttributes().put(WebComponent.LINKS_ATTR,links);
         Object scriptManager = context.getAttributes().get(ScriptManager.ATTR);
         if (scriptManager!=null) {
            appContext.getAttributes().put(ScriptManager.ATTR,scriptManager);
         }
         Object resourceManager = context.getAttributes().get(ResourceManager.ATTR);
         if (resourceManager!=null) {
            appContext.getAttributes().put(ResourceManager.ATTR,resourceManager);
         }
         for (String name : parameters.keySet()) {
            appContext.getLogger().info("Setting parameter "+name+"="+parameters.get(name));
            appContext.getParameters().add(name,parameters.get(name));
         }
         return appDef.getInstance(appContext);
      }

      public String getMatch()
      {
         return match;
      }

   }
   
   long autoconfCheck;
   Map<String,AppDef> appDefs;
   List<Interface> interfaces;
   File keyStorePath;
   String keyStorePassword;
   
   /** Creates a new instance of Configuration */
   public Configuration()
   {
      this.autoconfCheck = 180*1000;
      this.appDefs = new HashMap<String,AppDef>();
      this.interfaces = new ArrayList<Interface>();
   }
   
   
   public Host createHost(Element hostE)
      throws XMLException
   {
      String name = hostE.getAttributeValue("name");
      String internalName = hostE.getAttributeValue("internal");
      Host host = new Host(name,internalName);

      Iterator<Element> hostParams = hostE.getElementsByName(PARAMETER);
      while (hostParams.hasNext()) {
         Element paramE = hostParams.next();
         String pname = paramE.getAttributeValue("name");
         if (pname!=null) {
            String value = paramE.getAttributeValue("value");
            String href = paramE.getAttributeValue("href");
            if (href!=null) {
               value = paramE.getBaseURI().resolve(href).toString();
            }
            if (value==null) {
               value = "";
            }
            host.getParameters().put(pname,value);
         }
      }

      Iterator<Element> links = hostE.getElementsByName(LINK);
      while (links.hasNext()) {
         Element linkE = links.next();
         String href = linkE.getAttributeValue("href");
         if (href!=null) {
            URI tlocation = linkE.getBaseURI().resolve(href);
            String mtype = linkE.getAttributeValue("type");
            Link l = new Link(linkE.getAttributeValue("rel"),mtype==null ? null : MediaType.valueOf(mtype),tlocation);
            l.setIdentity(linkE.getAttributeValue("username"),linkE.getAttributeValue("password"));
            if (l.getRelation()!=null) {
               host.getLinks().put(l.getRelation(),l);
            }
         }
      }
      Iterator<Element> appElements = hostE.getElementsByName(APP);
      while (appElements.hasNext()) {
         Element appE = appElements.next();
         String appName = appE.getAttributeValue("name");
         String match = appE.getAttributeValue("match");
         AppDef def = appDefs.get(appName);
         if (def==null) {
            throw new XMLException("Cannot find defintion of application "+appName);
         }
         AppConf appconf = new AppConf(def,match,(Element)appE.copyOfItem(true),host.getLinks());

         Iterator<Element> appLinks = appE.getElementsByName(LINK);
         while (appLinks.hasNext()) {
            Element linkE = appLinks.next();
            String href = linkE.getAttributeValue("href");
            if (href!=null) {
               URI tlocation = linkE.getBaseURI().resolve(href);
               String mtype = linkE.getAttributeValue("type");
               Link l = new Link(linkE.getAttributeValue("rel"),mtype==null ? null : MediaType.valueOf(mtype),tlocation);
               l.setIdentity(linkE.getAttributeValue("username"),linkE.getAttributeValue("password"));
               if (l.getRelation()!=null) {
                  appconf.getLinks().put(l.getRelation(),l);
               }
            }
         }
         Iterator<Element> params = appE.getElementsByName(PARAMETER);
         while (params.hasNext()) {
            Element paramE = params.next();
            String pname = paramE.getAttributeValue("name");
            if (pname!=null) {
               String value = paramE.getAttributeValue("value");
               String href = paramE.getAttributeValue("href");
               if (href!=null) {
                  value = paramE.getBaseURI().resolve(href).toString();
               }
               if (value==null) {
                  value = "";
               }
               appconf.getParameters().put(pname,value);
            }
         }
         host.getApplications().add(appconf);
      }
      Iterator<Element> contentElements = hostE.getElementsByName(CONTENT);
      while (contentElements.hasNext()) {
         Element contentE = contentElements.next();
         String match = contentE.getAttributeValue("match");
         String href = contentE.getAttributeValue("href");
         URI source = contentE.getBaseURI().resolve(href);
         Content content = new Content(source,match);
         
         host.getContentSources().add(content);
      }

      Iterator<Element> logs = hostE.getElementsByName(LOG);
      if (logs.hasNext()) {
         Element logE = logs.next();
         for (Name attName : logE.getAttributes().keySet()) {
            host.getLogConfiguration().put(attName.toString(),logE.getAttributeValue(attName));
         }
      }
      return host;
      
   }
   public void load(URI location)
      throws IOException,XMLException
   {
      DocumentLoader loader = new SAXDocumentLoader();
      Document doc = loader.load(location);
      Element top = doc.getDocumentElement();
      if (!top.getName().equals(SERVER)) {
         throw new XMLException("Expecting "+SERVER+" but found "+top.getName());
      }
      String value = top.getAttributeValue("autoconf-check");
      if (value!=null) {
         autoconfCheck = Long.parseLong(value) * 1000;
      }
      
      Element keyStoreE = top.getFirstElementNamed(KEYSTORE);
      if (keyStoreE!=null) {
         URI fileRef = keyStoreE.getBaseURI().resolve(keyStoreE.getAttributeValue("file"));
         this.keyStorePath = new File(fileRef.getSchemeSpecificPart());
         this.keyStorePassword = keyStoreE.getAttributeValue("password");
      }
      
      Iterator<Element> appdefElements = top.getElementsByName(APPDEF);
      while (appdefElements.hasNext()) {
         Element appdefE = appdefElements.next();
         String name = appdefE.getAttributeValue("name");
         String className = appdefE.getAttributeValue("class");
         
         if (name!=null && className!=null) {
            List<URL> libraries = new ArrayList<URL>();
            Iterator<Element> libraryElements = appdefE.getElementsByName(LIBRARY);
            while (libraryElements.hasNext()) {
               Element libE = libraryElements.next();
               String href = libE.getAttributeValue("href");
               if (href!=null) {
                  URI u = libE.getBaseURI().resolve(href);
                  libraries.add(u.toURL());
               }
            }
            try {
               Class cdef = null;
               if (libraries.size()==0) {
                  cdef = this.getClass().forName(className);
               } else {
                  URL [] list = new URL[libraries.size()];
                  list = libraries.toArray(list);
                  ClassLoader urlLoader = new URLClassLoader(list,this.getClass().getClassLoader());
                  cdef = urlLoader.loadClass(className);
               }
               AppDef def = new AppDef(name,cdef);
               appDefs.put(name,def);
            } catch (ClassNotFoundException ex) {
               throw new XMLException("Cannot find class: "+ex.getMessage(),ex);
            }
         }
      }
      Iterator<Element> interfaceElements = top.getElementsByName(INTERFACE);
      while (interfaceElements.hasNext()) {
         Element interfaceE = interfaceElements.next();
         String addr = interfaceE.getAttributeValue("address");
         String portS = interfaceE.getAttributeValue("port");
         boolean secure = interfaceE.getAttributeValue("secure")==null || "true".equals(interfaceE.getAttributeValue("secure"));
         int port = portS==null ? (secure ? 443 : 80) : Integer.parseInt(portS);
         Interface iface = new Interface(addr,port,secure);
         interfaces.add(iface);
         
         Iterator<Element> links = interfaceE.getElementsByName(LINK);
         while (links.hasNext()) {
            Element linkE = links.next();
            String href = linkE.getAttributeValue("href");
            if (href!=null) {
               URI tlocation = linkE.getBaseURI().resolve(href);
               String mtype = linkE.getAttributeValue("type");
               Link l = new Link(linkE.getAttributeValue("rel"),mtype==null ? null : MediaType.valueOf(mtype),tlocation);
               l.setIdentity(linkE.getAttributeValue("username"),linkE.getAttributeValue("password"));
               if (l.getRelation()!=null) {
                  iface.getLinks().put(l.getRelation(),l);
               }
            }
         }
         
         Iterator<Element> hostElements = interfaceE.getElementsByName(HOST);
         while (hostElements.hasNext()) {
            Element hostE = hostElements.next();
            Host host = createHost(hostE);
            iface.getHosts().put(host.getName(),host);
         }
      }
      
   }
   
   public long getAutoConfigurationCheckWait() {
      return autoconfCheck;
   }
   
   public File getKeyStorePath()
   {
      return keyStorePath;
   }
   
   public String getKeyStorePassword()
   {
      return keyStorePassword;
   }
   
   public List<Interface> getInterfaces() {
      return interfaces;
   }

   public void setKeyStorePath(File keyStorePath)
   {
      this.keyStorePath = keyStorePath;
   }

   public void setKeyStorePassword(String keyStorePassword)
   {
      this.keyStorePassword = keyStorePassword;
   }
   
}
